/*******************************************************************************
 * Copyright (c) 2002 Boris Pruessmann and others. All rights reserved. This
 * program and the accompanying materials are made available under the terms of
 * the Common Public License v0.5 which accompanies this distribution, and is
 * available at http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors: Boris Pruessmann - Initial implementation.
 ******************************************************************************/
package net.sourceforge.perforce.core.internal.events;

import com.google.common.collect.Lists;

import net.sourceforge.perforce.core.IResourceStateChangeListener;
import net.sourceforge.perforce.core.PerforceCorePlugin;
import net.sourceforge.perforce.core.PerforceException;
import net.sourceforge.perforce.core.PerforceSynchronizer;
import net.sourceforge.perforce.core.api.IPerforceServer;
import net.sourceforge.perforce.core.internal.AdditionMarkerManager;
import net.sourceforge.perforce.core.internal.P4TeamProvider;
import net.sourceforge.perforce.core.resources.IChangelist;
import net.sourceforge.perforce.core.syncinfo.IResourceStateDelta;

import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IResourceVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.team.core.RepositoryProvider;
import org.eclipse.team.core.TeamException;

import java.util.List;

/**
 * @version $Revision: 488 $
 * @author <a href="mailto:bpruessmann@users.sourceforge.net">Boris
 *         Pruessmann</a>
 */
public class AddDeleteMoveListener implements IResourceChangeListener,
    IResourceStateChangeListener, IResourceDeltaVisitor {
  //----------------------------------------------------------------------------
  // Public Constants

  /** Id of Perforce marker category. */
  public static final String P4_MARKER = "net.sourceforge.perforce.core.p4marker"; //$NON-NLS-1$
  /** Id of addition markers. */
  public static final String ADDITION_MARKER = "net.sourceforge.perforce.core.p4add";//$NON-NLS-1$


  // ---------------------------------------------------------------------------
  // Private Variables

  /** Indicates that we are currently handling the PRE_DELETE event. */
  private boolean handlingPreDelete = false;

  // -----------------------------------------------------------------------
  // Public Static Methods

  /**
   * Called to refresh all perforce markers.
   * 
   * @throws CoreException in case of an error.
   */
  public static void refreshAllMarkers() throws CoreException {
    IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
    for (int i = 0; i < projects.length; i++) {
      IProject project = projects[i];
      if (RepositoryProvider.getProvider(project, PerforceCorePlugin.getTypeId()) != null) {
        refreshMarkers(project);
      }
    }
  }

  /**
   * Called to clear all perforce markers.
   * 
   * @throws CoreException in case of an error.
   */
  public static void clearAllPerforceMarkers() throws CoreException {
    IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
    for (int i = 0; i < projects.length; i++) {
      IProject project = projects[i];
      if (RepositoryProvider.getProvider(project, PerforceCorePlugin.getTypeId()) != null) {
        clearPerforceMarkers(project);
      }
    }
  }


  // ------------------------------------------------------ Public Methods
  // (IResourceDeltaVisitor)

  /** @see IResourceDeltaVisitor#visit(IResourceDelta) */
  public boolean visit(IResourceDelta delta) throws CoreException {
    try {
      IResource resource = delta.getResource();
      switch (delta.getKind()) {
        case IResourceDelta.ADDED:
          handleResourceAdded(resource);
          break;

        case IResourceDelta.REMOVED:
          handleResourceRemoved(resource);
          break;
      }

      return true;
    } catch (PerforceException e) {
      throw new CoreException(e.getStatus());
    }
  }

  // ---------------------------------------------------- Public Methods
  // (IResourceChangeListener)

  /** @see IResourceChangeListener#resourceChanged(IResourceChangeEvent) */
  public synchronized void resourceChanged(IResourceChangeEvent event) {
    try {
      if (event.getType() == IResourceChangeEvent.POST_AUTO_BUILD) {
        IResourceDelta root = event.getDelta();
        IResourceDelta[] projectDeltas = root.getAffectedChildren();
        for (int i = 0; i < projectDeltas.length; i++) {
          IResourceDelta delta = projectDeltas[i];
          IResource resource = delta.getResource();

          RepositoryProvider provider =
              RepositoryProvider.getProvider(resource.getProject(), PerforceCorePlugin.getTypeId());
          if (provider != null) {
            delta.accept(this);
          }
        }
      } else if (event.getType() == IResourceChangeEvent.PRE_DELETE) {
        handlingPreDelete = true;

        IResource resource = event.getResource();
        RepositoryProvider provider =
            RepositoryProvider.getProvider(resource.getProject(), PerforceCorePlugin.getTypeId());
        if (provider != null) {
          resource.accept(new IResourceVisitor() {
            public boolean visit(IResource resource) throws CoreException {
              try {
                // only handle folder deletes here because they
                // will also remove the file sync info
                if (resource.getType() != IResource.FILE) {
                  handleResourceRemoved(resource);
                }
              } catch (PerforceException e) {
                throw new CoreException(e.getStatus());
              }
              return true;
            }
          });
        }
      }
    } catch (CoreException e) {
      PerforceCorePlugin.log(e.getStatus());
    } finally {
      handlingPreDelete = false;
    }
  }

  // ----------------------------------------------- Public Methods
  // (IResourceStateChangeListener)

  public void resourceStateChanged(IResourceStateDelta[] resourceStateDelta) {
    if (handlingPreDelete) {
      return;
    }

    List<IResource> resourceList = Lists.newArrayList();
    for (int i = 0; i < resourceStateDelta.length; i++) {
      if (resourceStateDelta[i].getResource().exists()) {
        resourceList.add(resourceStateDelta[i].getResource());
      }
    }

    IResource[] changedResources = resourceList.toArray(new IResource[resourceList.size()]);
    AdditionMarkerManager.createNecessaryMarkers(changedResources);
  }

  public void changelistStateChanged(IChangelist[] changelists) {
  }

  public void changelistStateChanged(IPerforceServer[] servers) {
  }

  // ----------------------------------------------------------------------
  // Private Static Methods

  /**
   * Call this method to refresh the perforce markers of the given resource.
   * 
   * @param resource the resource.
   * @throws CoreException in case of an error.
   */
  private static void refreshMarkers(IResource resource) throws CoreException {
    final List<IResource> resources = Lists.newArrayList();
    clearPerforceMarkers(resource);
    resource.accept(new IResourceVisitor() {
      public boolean visit(IResource resource) {
        if (resource.getType() == IResource.FILE) {
          resources.add(resource);
        }

        return true;
      }
    }, IResource.DEPTH_INFINITE, true);

    AdditionMarkerManager
        .createNecessaryMarkers(resources.toArray(new IResource[resources.size()]));
  }

  /**
   * Call this method to clear the perforce markers of the given resource.
   * 
   * @param resource the resource.
   * 
   * @throws CoreException in case of an error.
   */
  private static void clearPerforceMarkers(IResource resource) throws CoreException {
    IMarker[] markers = resource.findMarkers(P4_MARKER, true, IResource.DEPTH_INFINITE);
    for (int i = 0; i < markers.length; i++) {
      markers[i].delete();
    }
  }

  /**
   * Called to handle resource adds.
   * 
   * @param resource the added resource.
   * 
   * @throws CoreException in case of an error.
   */
  private void handleResourceAdded(IResource resource) throws CoreException {
    if (resource.getType() == IResource.FOLDER) {
      AdditionMarkerManager.createNecessaryMarkers(new IResource[] {resource});
    } else if (resource.getType() == IResource.FILE) {
      try {
        P4TeamProvider provider =
            (P4TeamProvider) RepositoryProvider.getProvider(resource.getProject(),
                PerforceCorePlugin.getTypeId());
        if (provider != null) {
          if (PerforceSynchronizer.getInstance().getResourceSync(resource) == null) {
            provider.refreshState(new IResource[] {resource}, IResource.DEPTH_ONE, null);
          }
        }

        AdditionMarkerManager.createNecessaryMarkers(new IResource[] {resource});
      } catch (TeamException e) {
        throw new CoreException(e.getStatus());
      }
    }
  }

  /**
   * Called to handle resource deletions.
   * 
   * @param resource the removed resource.
   * 
   * @throws PerforceException in case of an error.
   */
  private void handleResourceRemoved(IResource resource) throws PerforceException {
    PerforceSynchronizer synchronizer = PerforceCorePlugin.getSynchronizer();
    if (resource.getType() == IResource.FILE) {
      synchronizer.deleteResourceSync(resource);
    } else {
      synchronizer.deleteFolderSync((IContainer) resource);
    }
  }
}
